﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pws.Clients.RestLibrary.Transport;
using PCRL = Pws.Clients.RestLibrary;
using PCRLS = Pws.Clients.RestLibrary.Shared;
using PCRLE = Pws.Clients.RestLibrary.ECommerce;
using PCRLC = Pws.Clients.RestLibrary.Customers;
using PCRLP = Pws.Clients.RestLibrary.Products;
using System.Net;
using System.Text.RegularExpressions;
using System.IO;

namespace Pws.Clients.RestLibrary.Service
{
	/*
	 * Copyright (C) PWS Distributors Ltd 2011-2013
	 * All rights reserved.
	 * 
	 */

	/// <summary>
	/// Implements the middle tier IPwsService interface by providing REST operations (e.g. Get and Put) for the PWS Objects from
	/// the Pws.Clients.RestLibrary assembly.
	/// This relies upon the transport layer implementation from Pws.Clients.RestLibrary.Transport
	/// which handles the Xml serialisation and HMAC checksums.
	/// 
	/// Instances and operations are thread safe.
	/// </summary>
	public class PwsService : IPwsService
	{
		/// <summary>
		/// An instance of the IRequestFactory from the transport layer.
		/// </summary>
		private readonly IRequestFactory _factory;

		/// <summary>
		/// Create a service proxy based upon a known deployment Uri.
		/// </summary>
		/// <param name="apiKey">The public API Key supplied to the customer.</param>
		/// <param name="privateKey">The private key supplied to the customer, used for signing messages.</param>
		/// <param name="deploymentUri">Web Services deployment address, e.g http://services.pws.co.uk:8080/rest </param>
		public PwsService(Guid apiKey, Guid privateKey, String deploymentUri)
		{
			_factory = new RequestFactory(apiKey, privateKey, deploymentUri);
		}

		/// <summary>
		/// Create a service proxy based upon a particular PWS deployment.
		/// </summary>
		/// <param name="apiKey">The public API Key supplied to the customer.</param>
		/// <param name="privateKey">The private key supplied to the customer, used for signing messages.</param>
		/// <param name="type">Deployment version to connect with.</param>
		public PwsService(Guid apiKey, Guid privateKey, DeploymentType type)
		{
			_factory = new RequestFactory(apiKey, privateKey, type);
		}

		/// <summary>
		/// Returns the object which is located at the root level of the service. This is always the entry point for the REST services.
		/// </summary>
		/// <typeparam name="T">Object type to return. E.g. Services_V1.</typeparam>
		/// <returns>An object describing the root service.</returns>
		public T GetRoot<T>() where T : class
		{
			try
			{
				return Get<T>(new Uri(_factory.DeploymentUri + "/"));
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return default(T);
			}
		}

		/// <summary>
		/// Perform a REST Web Service call to PWS, with token based user authentication. The HMAC is also included in the request, implicitly.
		/// This will attempt to use the DataContractSerializer on the supplied and retrieved objects.
		/// The Media Type will be determined from the object type T.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <returns>Object of type T from service.</returns>
		/// <typeparam name="T">The type of object being received. This will be used for automatic serialisation to Xml.</typeparam>
		public T Get<T>(Uri service, PCRLE.Users.Token_V1 tokenV1) where T : class
		{
			try
			{
				return _factory.GetRestResponseAsObject<T>("GET", service, PCRL.MediaTypes.Get(typeof(T)), null, tokenV1 != null ? tokenV1.Token : null);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return default(T);
			}
		}

		/// <summary>
		/// Perform a REST Web Service call to PWS, with token based user authentication. The HMAC is also included in the request, implicitly.
		/// This will attempt to use the DataContractSerializer on the supplied and retrieved objects.
		/// The Media Type will be determined from the object type T. This will obtain a single page of results for best performance, with
		/// links to other pages if applicable.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <param name="nextPage">A link to the next page of the list. This will be null if there are no more pages of results.</param>
		/// <param name="previousPage">A link to the previous page of the list. This will be null if there are no previous pages of results.</param>
		/// <returns>List of objects of type T from service, representing just one page of results for best performance.</returns>
		/// <typeparam name="T">The type of object being received. This will be used for automatic serialisation to Xml.</typeparam>
		public IList<T> GetList<T>(Uri service, PCRLE.Users.Token_V1 tokenV1, out Uri previousPage, out Uri nextPage) where T : class
		{
			WebHeaderCollection httpResponseHeaders = null;

			// Call the service and get the list of objects.
			IList<T> result = null;
			try
			{
				result = _factory.GetRestResponseAsObject<IList<T>>("GET", service, PCRL.MediaTypes.Get(typeof(T)), null, tokenV1 != null ? tokenV1.Token : null, out httpResponseHeaders);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				previousPage = null;
				nextPage = null;
				return null;
			}

			previousPage = null;
			nextPage = null;

			// Parse the previous and next page links.
			IEnumerable<String> links = httpResponseHeaders.GetValues("Link");
			if (links != null)
			{
				// Split link header into multiple lines.
				links = links.SelectMany(f => f.Split(','));

				foreach (String link in links)
				{
					Match previous = Regex.Match(link, "^<(?<uri>.+)>.*(?:rel=\\\"previous\\\")");
					if (previous.Success)
					{
						previousPage = new Uri(previous.Result("${uri}"));
					}

					Match next = Regex.Match(link, "^<(?<uri>.+)>.*(?:rel=\\\"next\\\")");
					if (next.Success)
					{
						nextPage = new Uri(next.Result("${uri}"));
					}
				}
			}

			return result;
		}

		/// <summary>
		/// Perform a REST Web Service call to PWS, with token based user authentication. The HMAC is also included in the request, implicitly.
		/// This will attempt to use the DataContractSerializer on the supplied and retrieved objects.
		/// The Media Type will be determined from the object type T. This will obtain all pages of results, by repeatedly calling
		/// to get each page in turn. Please use the other overload if supporting pagination.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <returns>List of objects of type T from service.</returns>
		/// <typeparam name="T">The type of object being received. This will be used for automatic serialisation to Xml.</typeparam>
		public IList<T> GetList<T>(Uri service, PCRLE.Users.Token_V1 tokenV1) where T : class
		{
			List<T> results = new List<T>();

			Uri nextPage = service;
			Uri previousPage = null;

			try
			{
				do
				{
					IList<T> pageResults = GetList<T>(nextPage, tokenV1, out previousPage, out nextPage);
					results.AddRange(pageResults);
				}
				while (nextPage != null);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return null;
			}

			return results;
		}

		/// <summary>
		/// Perform a REST Web Service call to PWS, with string based user authentication. The HMAC is also included in the request, implicitly.
		/// This will attempt to use the DataContractSerializer on the supplied and retrieved objects.
		/// The Media Type will be determined from the object type T.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="username">Username for authentication.</param>
		/// <param name="password">Password for authentication.</param>
		/// <param name="sendObject">Object to send.</param>
		/// <returns>Object of type T from service.</returns>
		/// <typeparam name="T">The type of object being sent. This will be used for automatic serialisation to Xml.</typeparam>
		public T Post<T>(Uri service, T sendObject, string username, string password) where T : class
		{
			try
			{
				return _factory.GetRestResponseAsObject<T>("POST", service, PCRL.MediaTypes.Get(typeof(T)), sendObject, username, password);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return default(T);
			}
		}

		/// <summary>
		/// Perform a REST Web Service call to PWS, with token based user authentication. The HMAC is also included in the request, implicitly.
		/// This will attempt to use the DataContractSerializer on the supplied and retrieved objects.
		/// The Media Type will be determined from the object type T.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <returns>Object of type T from service.</returns>
		/// <typeparam name="T">The type of object being sent. This will be used for automatic serialisation to Xml.</typeparam>
		public T Post<T>(Uri service, T sendObject, PCRLE.Users.Token_V1 tokenV1) where T : class
		{
			try
			{
				return _factory.GetRestResponseAsObject<T>("POST", service, PCRL.MediaTypes.Get(typeof(T)), sendObject, tokenV1 != null ? tokenV1.Token : null);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return default(T);
			}
		}

		/// <summary>
		/// Perform a REST Web Service call to PWS, with token based user authentication. The HMAC is also included in the request, implicitly.
		/// This will attempt to use the DataContractSerializer on the supplied and retrieved objects.
		/// The Media Type will be determined from the object type T.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <returns>Object of type T from service.</returns>
		/// <typeparam name="T">The type of object being sent. This will be used for automatic serialisation to Xml.</typeparam>
		public T Put<T>(Uri service, T sendObject, PCRLE.Users.Token_V1 tokenV1) where T : class
		{
			try
			{
				return _factory.GetRestResponseAsObject<T>("PUT", service, PCRL.MediaTypes.Get(typeof(T)), sendObject, tokenV1 != null ? tokenV1.Token : null);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return default(T);
			}
		}

		/// <summary>
		/// Perform a REST Web Service call to PWS, with token based user authentication. The HMAC is also included in the request, implicitly.
		/// This will attempt to use the DataContractSerializer on the supplied and retrieved objects.
		/// The Media Type will be determined from the object type T.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <returns>Object of type T from service..</returns>
		/// <typeparam name="T">The type of object being sent. This will be used for automatic serialisation to Xml.</typeparam>
		public void Delete<T>(Uri service, T sendObject, PCRLE.Users.Token_V1 tokenV1) where T : class
		{
			try
			{
				_factory.GetRestResponseAsObject<T>("DELETE", service, PCRL.MediaTypes.Get(typeof(T)), sendObject, tokenV1 != null ? tokenV1.Token : null);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return;
			}
		}

		/// <summary>
		/// Get a binary file, e.g. a PDF.
		/// </summary>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="mediaType">The media type being requested.</param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <returns>A newly retrieved file within the standard Windows temporary directory,</returns>
		public FileInfo GetBinaryFile(Uri service, string mediaType, PCRLE.Users.Token_V1 tokenV1)
		{
			try
			{
				using (WebResponse response = _factory.GetRestResponse("GET", service, null, mediaType, null, tokenV1.Token))
				using (Stream responseObjectStream = response.GetResponseStream())
				{
					String path = Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString() + "." + mediaType.Split('/')[1]);
					using (FileStream fs = new FileStream(path, FileMode.CreateNew))
					{
						responseObjectStream.CopyTo(fs);
					}

					return new FileInfo(path);
				}
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return null;
			}
		}

		/// <summary>
		/// Post a binary file, e.g. a PDF, and get back an object representing the upload.
		/// </summary>
		/// <typeparam name="T">The type of object being received.</typeparam>
		/// <param name="service">The absolute URI to the service and object being called. </param>
		/// <param name="mediaType">The media type of the file being sent.</param>
		/// <param name="stream">The data stream being Posted.</param>
		/// <param name="tokenV1">A user authentication token which has been provided by the PWS Authentication Token Service.</param>
		/// <returns>Object of type T from service, e.g. Upload_V1.</returns>
		public T PostBinaryFile<T>(Uri service, Stream stream, string mediaType, PCRLE.Users.Token_V1 tokenV1) where T : class
		{
			try
			{
				WebHeaderCollection httpResponseHeaders;
				return _factory.GetRestResponseAsObject<T>("POST", service, mediaType, PCRL.MediaTypes.Get(typeof(T)), stream, tokenV1.Token, out httpResponseHeaders);
			}
			catch (Exception e)
			{
				ExceptionFactory.ThrowException(e);
				return default(T);
			}
		}

		#region Overloads without tokens

		/// <summary>
		/// Overload without token.
		/// </summary>
		public T Get<T>(Uri service) where T : class
		{
			return Get<T>(service, null);
		}

		/// <summary>
		/// Overload without token.
		/// </summary>
		public IList<T> GetList<T>(Uri service, out Uri previousPage, out Uri nextPage) where T : class
		{
			return GetList<T>(service, null, out previousPage, out nextPage);
		}

		/// <summary>
		/// Overload without token.
		/// </summary>
		public IList<T> GetList<T>(Uri service) where T : class
		{
			return GetList<T>(service, null);
		}

		#endregion
	}
}
